#pragma once

#include "Mesh.h"
#include <glut.h>

/**
 * @file Camera.h
 * @author xingyifan
 * @date 2023-01-10 21:34
 *
 * @description: 控制摄像机
 */

class Camera
{
public:
    CG::point3f m_viewer;      // 观察者位置
    int m_screenX, m_screenY;  // 屏幕坐标
    double m_angleX, m_angleY; // 显示旋转角
    double m_fixX, m_fixY;     // 固定旋转角
    double m_dis;              // 镜头距离
    bool m_moving;             // 记录移动状态

    LA::Matrix m_Model;   // 模型视图矩阵
    LA::Matrix m_Project; // 投影矩阵

public:
    Camera(CG::point3f viewer)
        : m_screenX(0), m_screenY(0), m_angleX(0), m_angleY(0), m_fixX(0), m_fixY(0),
          m_moving(false), m_viewer(viewer), m_Model(LA::Matrix(4, 4)), m_Project(LA::Matrix(4, 4))
    {
        // 初始化镜头距离
        m_dis = CG::length(m_viewer - CG::point3f());
    }

    /**
     * @file Scene.h
     * @author xingyifan
     * @date 2022-12-31 22:33
     *
     * @description: 窗口操作
     */

    // 刷新相机位置等属性
    void Refresh()
    {
        // 载入单位阵
        glLoadIdentity();

        // 移动视角
        const double PI = 3.1415926;
        m_viewer.x = m_dis * cos(m_angleY / 360 * 2 * PI) * sin(m_angleX / 360 * 2 * PI);
        m_viewer.y = m_dis * sin(m_angleY / 360 * 2 * PI);
        m_viewer.z = m_dis * cos(m_angleY / 360 * 2 * PI) * cos(m_angleX / 360 * 2 * PI);

        // 设置摄像机
        gluLookAt(0, 0, CG::length(m_viewer - CG::point3f()),
                  0, 0, 0,
                  0, 1, 0);

        // 旋转摄像机角度
        glRotatef(m_angleY, 1, 0, 0);
        glRotatef(m_angleX, 0, 1, 0);

        // 获取变换矩阵
        double m[16], p[16];
        glGetDoublev(GL_MODELVIEW_MATRIX, m);
        glGetDoublev(GL_PROJECTION_MATRIX, p);

        // 注意矩阵的行列要转置
        for (int i = 0; i < 4; i++)
        {
            for (int j = 0; j < 4; j++)
            {
                m_Model(j, i) = m[i * 4 + j];
                m_Project(j, i) = p[i * 4 + j];
            }
        }
    }

    // 开始移动，记录屏幕坐标
    void StartMove(int x, int y)
    {
        m_moving = true;
        m_screenX = x;
        m_screenY = y;
    }

    // 移动操作
    bool Moving(int x, int y)
    {
        // 如果移动就修改参数
        if (m_moving)
        {
            int dx = x - m_screenX;
            int dy = y - m_screenY;
            m_angleX = m_fixX + dx / 5.0;
            m_angleY = m_fixY + dy / 5.0;

            return true;
        }
        return false;
    }

    // 停止移动，参数固定
    void StopMove(int x, int y)
    {
        m_moving = false;
        m_screenX = x;
        m_screenY = y;
        m_fixX = m_angleX;
        m_fixY = m_angleY;
    }

    // 滚轮滑动，移动距离，同时刷新窗口
    void Wheeling(short zDelta)
    {
        if (zDelta > 0)
            m_dis -= 1;
        else
            m_dis += 1;
    }

    /**
     * @file Scene.h
     * @author xingyifan
     * @date 2023-01-10 23:40
     *
     * @description: 映射函数
     */

    // 投影到正交视景体
    CG::point3f MapstoOrtho(CG::point3f p)
    {
        LA::Vector v = {p.x, p.y, p.z, 1};

        // 计算投影点
        v = m_Project * m_Model * v;
        v = v / v[3];
        return {v[0], v[1], v[2]};
    }

    // 投影到透视视景体
    CG::point3f MapstoPersp(CG::point3f p)
    {
        LA::Vector v = {p.x, p.y, p.z, 1};

        // 计算投影点
        v = m_Model.inverse() * m_Project.inverse() * v;
        v = v / v[3];
        return {v[0], v[1], v[2]};
    }
};